<?php

/**
 * Clase que maneja cadenas de caracteres y las convierte en cosas útiles
 */
class Variables {

  /**
   * Función que encripta una cadena
   *
   * @global String $strGKey Llave de encriptación
   * @param  String $strCadena Cadena a encriptar
   * @return String Cadena encriptada
   */
  static function EncodeString($strCadena) {
    global $strGKey;
    return mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $strGKey, $strCadena, MCRYPT_MODE_ECB, 'abcdefghijklmnopqrstuvwxyz012345');
  }

  /**
   * Función que desencripta una cadena previamente encriptada
   *
   * @global String $strGKey   Llave de encriptación
   * @param  String $strCadena Cadena a desencriptar
   * 
   * @return String Cadena desencriptada
   */
  static function DecodeString($strCadena) {
    global $strGKey;
    return rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $strGKey, $strCadena, MCRYPT_MODE_ECB, 'abcdefghijklmnopqrstuvwxyz012345'), "\0");
  }

  /**
   * Obtiene la opción actual de la página (?option=algo)
   *
   * @uses Variables::GetOption
   *
   * @return String la opción de la página
   */
  static function GetPageOption() {
    return Variables::GetOption('option');
  }

  /**
   * Obtiene un parametro de la página (?param=value)
   * 
   * @param String $strOption el parametro del cual se quiere conocer su valor
   * 
   * @return String el valor del parametro requerido o "" si dicho parametro no se encuentra
   */
  static function GetOption($strOption) {
    if(isset($_POST[$strOption])) {
      return $_POST[$strOption];
    } else if(isset($_GET[$strOption])) {
      return $_GET[$strOption];
    } else {
      return '';
    }
  }
  
  /**
   * Funcion que obtiene un arreglo desde una consulta SQL.
   * 
   * Se obtienen solo las columnas Indice y Desc, no las demas columnas que 
   * estan en la consulta
   * 
   * @param String $strConsulta  La consulta que se ejecuta para obtener los datos
   * @param String $strColIndice El nombre de la columna ID de la tabla
   * @param String $strColDesc   El nombre de la columna que se desplegará
   * 
   * @return array Arreglo con los datos
   */
  static function GetArrayByQuery($strConsulta, $strColIndice, $strColDesc) {
    $oConecta = new Conecta();
    
    $oConecta->Consulta($strConsulta);
    
    $arrRes = array();
    while(!$oConecta->EOF) {
      $arrRes[$oConecta->Field($strColIndice)] = $oConecta->Field($strColDesc);
      $oConecta->MoveNext();
    }
    return $arrRes;
  }
  
  /**
   * Funcion que obtiene un arreglo desde una tabla
   * 
   * Se obtienen solo las columnas Indice y Desc, no las demas columnas que 
   * estan en la consulta
   * 
   * @param String $strConsulta  La consulta que se ejecuta para obtener los datos
   * @param String $strColIndice El nombre de la columna ID de la tabla
   * @param String $strColDesc   El nombre de la columna que se desplegará
   * @param String $strOrder     La columna por la cual se ordenaran los datos (Sin el ORDER BY)
   * @param String $strWhere     La condicion que se tiene que cumplir para que se obtengan los datos (Sin el WHERE)
   * 
   * @return array Arreglo con los datos
   */
  static function GetArrayByTable($strTabla, $strColIndice, $strColDesc, $strOrder = "", $strWhere = "") {
    $strConsulta = "SELECT $strColIndice AS xx_Indice, $strColDesc AS xx_Desc FROM $strTabla ";
    if($strWhere) {
      $strConsulta .= "WHERE $strWhere ";
    }
    
    if($strOrder) {
      $strConsulta .= "ORDER BY $strOrder";
    }
    
    return Variables::GetArrayByQuery($strConsulta, 'xx_Indice', 'xx_Desc');
  }

  /**
   * Obtiene el código de un SELECT de HTML con los datos de una tabla.
   *
   * @uses Variables::GetSelectByArray
   *
   * @param String $strTabla              El nombre de la tabla que contiene las opciones
   * @param String $strColIndice          El nombre de la columna ID de la tabla
   * @param String $strColDesc            El nombre de la columna que se desplegará
   * @param String $strColSeleccion       Si se tiene que seleccionar de antemano un dato, aquí se pasa el ID de dicho dato, si no se deja en blanco
   * @param String $strNombreSelect       El nombre que se le pondrá al select. Si este dato y el de abajo están en blanco, no se regresarán las cabeceras <select></select>
   * @param String $strExtraCabecera      Cosas extra que poner dentro de la cabecera del select
   * @param String $strExtraOpcionesQuery Opciones extra del query que obtendrá los datos
   * @param array  $arrOpciones           Si se pone un arreglo, este contendrá opciones extra a seleccionar
   *
   * @return String Código HTML del select
   */
  static function GetSelectByTable($strTabla, $strColIndice, $strColDesc, $strColSeleccion = '', $strNombreSelect = '', $strExtraCabecera = '', $strExtraOpcionesQuery = '', $arrOpciones = "") {
    $oConecta = new Conecta();
    $strConsulta = "SELECT $strColIndice AS xx_indice, $strColDesc AS xx_valor FROM $strTabla $strExtraOpcionesQuery";
    
    $oConecta->Consulta($strConsulta);

    if(is_array($arrOpciones)) {
      
    } else {
      $arrOpciones = Array();
    }
    
    while(!$oConecta->EOF) {
      $arrOpciones[$oConecta->Field('xx_indice')] = $oConecta->Field('xx_valor');
      $oConecta->MoveNext();
    }

    return Variables::GetSelectByArray($arrOpciones, $strColSeleccion, $strNombreSelect, $strExtraCabecera);
  }
  
  /**
   * Funcion que obtiene el codigo JS para un arreglo desde una tabla
   * 
   * @param String $strTabla         El nombre de la tabla que contiene las opciones
   * @param String $strColIndice     El nombre de la columna ID de la tabla. Si es una cadena separada por comas, se obtendrá un arreglo bidimensional (o tridimensional) con los valores. Si la cadena contiene un CONCAT no se hara la bidimensionalidad. Por el momento solo se aceptan hasta 4 niveles
   * @param String $strColDesc       El nombre de la columna que se desplegará
   * @param String $strNombreArreglo Nombre del arreglo
   * @param String $strOrden         Orden que se le pasara a la consulta (Sin el ORDER BY)
   * @param String $strWhere         Condicion que se tiene que cumplir para los datos (Sin el WHERE)
   * 
   * @return String Cadena con el codigo JS para la creacion del arreglo
   */
  static function GetJSArrayByTable($strTabla, $strColIndice, $strColDesc, $strNombreArreglo, $strOrden = "", $strWhere = "") {
    $oConecta         = new Conecta();
    $bolIncluyeCONCAT = false;
    $strCodigoEntrada = "";
    
    if(strpos($strColIndice, "CONCAT") !== false) {
      $bolIncluyeCONCAT = true;
      $strCodigoEntrada = '$arrDatos[$oConecta->Field("xx_indice")] = $oConecta->Field("xx_valor");';
    } else {
      $arrIndices = Variables::ArrayTrim(explode(",", $strColIndice));
      $strCodigoEntrada    = '$arrDatos';
      foreach($arrIndices as $strIndice) {
        $strCodigoEntrada .= '[$oConecta->Field("' . $strIndice . '")]';
      }
      $strCodigoEntrada   .= ' = $oConecta->Field("xx_valor");';
    }
    
    $strConsulta  = "";
    $strConsulta .= "SELECT ";
    if($bolIncluyeCONCAT) {
      $strConsulta .= "$strColIndice AS xx_indice, ";
    } else {
      $strConsulta .= "$strColIndice, ";
    }
    $strConsulta .= "$strColDesc AS xx_valor ";
    $strConsulta .= "FROM $strTabla ";
    
    if($strWhere) {
      $strConsulta .= "WHERE $strWhere ";
    }
    
    if($strOrden) {
      $strConsulta .= "ORDER BY $strOrden ";
    } else {
      $strConsulta .= "ORDER BY $strColIndice ";
    }
    
    $oConecta->Consulta($strConsulta);
    
    while(!$oConecta->EOF) {
      eval($strCodigoEntrada);
      $oConecta->MoveNext();
    }
    
    return Variables::GetJSArrayByArray($arrDatos, $strNombreArreglo);
  }
  
  /**
   * Funcion que obtiene el codigo JS para un arreglo desde un arreglo.
   * 
   * Si el arreglo es de mas de una dimension, el codigo tambien armara un 
   * arreglo de mas de una dimension
   * 
   * @param String $arrOpciones      El nombre de la tabla que contiene las opciones
   * @param String $strNombreArreglo Nombre del arreglo
   * 
   * @return String Cadena con el codigo JS para la creacion del arreglo
   */
  static function GetJSArrayByArray($arrOpciones, $strNombreArreglo) {
    $strRes  = "";
    $strRes .= "$strNombreArreglo = new Array();\n";
    foreach($arrOpciones as $strIndNivel1 => $varValorNivel1) {
      if(is_array($varValorNivel1)) {
        $strRes .= "{$strNombreArreglo}['{$strIndNivel1}'] = Array();\n";
        foreach($varValorNivel1 as $strIndNivel2 => $varValorNivel2) {
          if(is_array($varValorNivel2)) {
            $strRes .= "{$strNombreArreglo}['{$strIndNivel1}']['{$strIndNivel2}'] = Array();\n";
            foreach($varValorNivel2 as $strIndNivel3 => $varValorNivel3) {
              if(is_array($varValorNivel3)) {
                $strRes .= "{$strNombreArreglo}['{$strIndNivel1}']['{$strIndNivel2}']['{$strIndNivel3}'] = Array();\n";
                foreach($varValorNivel3 as $strIndNivel4 => $varValorNivel4) {
                  if(is_array($varValorNivel4)) {
                    Variables::DebugBacktrace("Demasiados niveles de arreglo");
                  } else {
                    $strRes .= "{$strNombreArreglo}['{$strIndNivel1}']['{$strIndNivel2}']['{$strIndNivel3}']['{$strIndNivel4}'] = '$varValorNivel4';\n";
                  }
                }
              } else {
                $strRes .= "{$strNombreArreglo}['{$strIndNivel1}']['{$strIndNivel2}']['{$strIndNivel3}'] = '$varValorNivel3';\n";
              }
            }
          } else {
            $strRes .= "{$strNombreArreglo}['{$strIndNivel1}']['{$strIndNivel2}'] = '$varValorNivel2';\n";
          }
        }
      } else {
        $strRes .= "{$strNombreArreglo}['{$strIndNivel1}'] = '$varValorNivel1';\n";
      }
    }
    
    return $strRes;
  }

  /**
   * Obtiene el código de un SELECT de HTML con los datos de una tabla.
   *
   * @uses Variables::GetSelectByArray
   *
   * @param String $strConsulta           La consulta que obtendrá los datos. Se tiene que especificar el indice (como xxIndice) y la columna nombre (como xxNombre)
   * @param String $strColSeleccion       Si se tiene que seleccionar de antemano un dato, aquí se pasa el ID de dicho dato, si no se deja en blanco
   * @param String $strNombreSelect       El nombre que se le pondrá al select. Si este dato y el de abajo están en blanco, no se regresarán las cabeceras <select></select>
   * @param String $strExtraCabecera      Cosas extra que poner dentro de la cabecera del select
   * @param String $strExtraOpcionesQuery Opciones extra del query que obtendrá los datos
   *
   * @return String Código HTML del select
   */
  static function GetSelectByQuery($strConsulta, $strColSeleccion = '', $strNombreSelect = '', $strExtraCabecera = '', $strExtraOpcionesQuery = '') {
    $oConecta = new Conecta();

    $oConecta->Consulta($strConsulta);

    $arrOpciones = Array();
    while(!$oConecta->EOF) {
      $arrOpciones[$oConecta->Field('xxIndice')] = $oConecta->Field('xxNombre');
      $oConecta->MoveNext();
    }

    return Variables::GetSelectByArray($arrOpciones, $strColSeleccion, $strNombreSelect, $strExtraCabecera);
  }

  /**
   * Obtiene el código de un SELECT de HTML con los datos de un arreglo.
   *
   * @param Array  $arrDatos         El arreglo con los datos que llenarán el select
   * @param String $strColSeleccion  Si se tiene que seleccionar de antemano un dato, aquí se pasa el ID de dicho dato, si no se deja en blanco
   * @param String $strNombreSelect  El nombre que se le pondrá al select. Si este dato y el de abajo están en blanco, no se regresarán las cabeceras <select></select>
   * @param String $strExtraCabecera Cosas extra que poner dentro de la cabecera del select
   *
   * @return String Código HTML del select
   */
  static function GetSelectByArray($arrDatos, $strColSeleccion = '', $strNombreSelect = '', $strExtraCabecera = '') {
    $strSelect = '';

    if(($strNombreSelect != '') || ($strExtraCabecera != '')) {
      $strSelect .= '<select ';

      if($strNombreSelect != '') {
        $strSelect .= 'name="' . $strNombreSelect . '" ';
      }

      $strSelect .= $strExtraCabecera;
      $strSelect .= '>';
    }

    foreach($arrDatos as $strIndice => $strNombre) {
      $strSelect .= '<option value="' . $strIndice . '"';
      if($strIndice == $strColSeleccion) {
        $strSelect .= ' SELECTED';
      }
      $strSelect .= '>' . $strNombre . '</option>';
    }

    if(($strNombreSelect != '') || ($strExtraCabecera != '')) {
      $strSelect .= '</select>';
    }

    return $strSelect;
  }

  /**
   * Función que obtiene una lista de checkboxes (U opcionalmente radios) con los parametros traidos de una tabla.
   *
   * @uses Variables::GetCheckboxesByArray
   *
   * @param String  $strTabla                           Tabla desde la que se leerá la información
   * @param String  $strColIndice                       Columna ID que identificará a cada checkbox (Este será el valor del checkbox)
   * @param String  $strColDesc                         Columna que nombrará al checkbox
   * @param Array   $arrSeleccion                       Arreglo con los checkboxes seleccionados por default. Puede ser una cadena separada por "|"
   * @param String  $strNombreCheckbox                  Nombre que se pondrá a cada checkbox. Se recomienda que si es checkbox termine con [] y si es radio que no termine con []
   * @param int     $intCheckboxesPorLinea              Numero de checkboxes que cada linea contendrá
   * @param Boolean $bolAsRadio                         Si es verdadero, en vez de regresar checkboxes se regresarán radios
   * @param String  $strExtraParametrosTablaContenedora Parametros extra (como clase) en la tabla que contendrá a los checkboxes
   * @param String  $strExtraParametrosQuery Parametros extra para el query que traerá los datos de la tabla
   * @param String  $strExtraParametrosCheckBox         Parametros extra que tendrá cada uno de los checkboxes
   * @param Boolean $bolAlternarColumnas                Si se pondrá la clase alternadora (ColumnaAlterna) en cada uno de los TR
   *
   * @return String Tabla HTML con los checkboxes (o radios) de la tabla
   */
  static function GetCheckboxesByTable($strTabla, $strColIndice, $strColDesc, $arrSeleccion = '', $strNombreCheckbox = '', $intCheckboxesPorLinea = 3, $bolAsRadio = false, $strExtraParametrosTablaContenedora = '', $strExtraParametrosQuery = '', $strExtraParametrosCheckBox = '', $bolAlternarColumnas = false) {
    $oConecta = new Conecta();
    $strConsulta = "SELECT $strColIndice AS xx_indice, $strColDesc AS xx_desc FROM $strTabla $strExtraParametrosQuery";
    $oConecta->Consulta($strConsulta);

    $arrDatos = Array();
    while(!$oConecta->EOF) {
      $arrDatos[$oConecta->Field('xx_indice')] = $oConecta->Field('xx_desc');
      $oConecta->MoveNext();
    }

    return Variables::GetCheckboxesByArray($arrDatos, $arrSeleccion, $strNombreCheckbox, $intCheckboxesPorLinea, $bolAsRadio, $strExtraParametrosTablaContenedora, '', 0, $strExtraParametrosCheckBox, $bolAlternarColumnas);
  }

  /**
   * Función que obtiene una lista de checkboxes (U opcionalmente radios) con los parametros de un arreglo de datos.
   *
   * @param Array   $arrOpciones                        Arreglo que contendrá la información para crear la lista de checkboxes
   * @param String  $arrSeleccion                       Arreglo con los checkboxes seleccionados por default. Puede ser una cadena separada por "|"
   * @param String  $strNombreCheckbox                  Nombre que se pondrá a cada checkbox. Se recomienda que si es checkbox termine con [] y si es radio que no termine con []
   * @param String  $intNumCols                         Numero de checkboxes que cada linea contendrá
   * @param String  $bolAsRadio                         Si es verdadero, en vez de regresar checkboxes se regresarán radios
   * @param String  $strExtraParametrosTablaContenedora Parametros extra (como clase) en la tabla que contendrá a los checkboxes
   * @param String  $arrExtraClasesSiContieneValor      Si el valor contenido es alguno de los de este arreglo, se agregará la clase descrita. El formato es el siguiente:
   *    $arr["nombreClase"][] = "valor a checar";
   * @param int     $intLimitarTexto                    Longitud maxima del texto de cada columna
   * @param String  $strExtraParametrosCheckBox         Parametros extra que tendrá cada uno de los checkboxes
   * @param Boolean $bolAlternarColumnas                Si se pondrá la clase alternadora (ColumnaAlterna) en cada uno de los TR
   * 
   * @return String Tabla HTML con los checkboxes (o radios) del arreglo
   */
  static function GetCheckboxesByArray($arrOpciones, $arrSeleccion, $strNombreCheckbox, $intNumCols = 2, $bolAsRadio = false, $strExtraParametrosTablaContenedora = '', $arrExtraClasesSiContieneValor = '', $intLimitarTexto = 0, $strExtraParametrosCheckBox = '', $bolAlternarColumnas = false) {
    if($bolAsRadio) {
      $strElemento = 'radio';
      $strSeleccionado = ' CHECKED';
    } else {
      $strElemento = 'checkbox';
      $strSeleccionado = ' CHECKED';
    }

    $i = 0;
    $strRes = "<table $strExtraParametrosTablaContenedora>";
    if(!is_array($arrSeleccion)) {
      $arrSeleccion = explode('|', $arrSeleccion);
    }
    
    $strColAlt = '';
    foreach($arrOpciones as $strLlave => $strValor) {
      if(!($i % $intNumCols)) {
        if($bolAlternarColumnas) {
          $strColAlt = "class='ColumnasAlternas" . Variables::Alternar() . "'";
        }
        if($i == 0) {
          $strRes .= "<tr $strColAlt>";
        } else {
          $strRes .= "</tr><tr $strColAlt>";
        }
      }
      $strRes .= '<td>';
      $strRes .= '<input ' . $strExtraParametrosCheckBox . ' type="' . $strElemento . '" name="' . $strNombreCheckbox . '" value="' . $strLlave . '"';

      if(in_array($strLlave, $arrSeleccion)) {
        $strRes .= $strSeleccionado;
      }
      
      $strClases = '';
      
      if(is_array($arrExtraClasesSiContieneValor)) {
        foreach($arrExtraClasesSiContieneValor as $strNombreClase => $arrValores) {
          if(isset($arrValores[$strLlave])) {
            $strClases .= $strNombreClase . ' ';
          }
        }
      }
      
      if($strClases) {
        $strRes .= ' class="' . substr($strClases, 0, -1) . '"';
      }

      $strRes .= '> ';
      if($intLimitarTexto) {
        $strRes .= '<span title="' . $strValor . '">' . substr($strValor, 0, $intLimitarTexto) . (strlen($strValor) > $intLimitarTexto ? '...' : '') . '</span>';
      } else {
        $strRes .= $strValor;
      }
      $strRes .= '</td>';
      $i++;
    }

    if($i > 0) {
      $strRes .= '</tr>';
    }
    $strRes .= '</table>';

    return $strRes;
  }
  
  /**
   * Obtiene un valor de una tabla
   * 
   * @param string  $strTabla     Tabla que contiene la informacion
   * @param string  $strColIndice Nombre de la columna indice (generalmente el id de la tabla)
   * @param string  $strColDesc   Nombre de la columna que se regresará, puede ser de la forma "columna" o "CONCAT(columna1, columna2)"
   * @param string  $strSeleccion El indice que se seleccionará
   * @param boolean $bolMultipleS Si es verdadero, se incluirán TODOS los valores
   */
  static function GetValueByTable($strTabla, $strColIndice, $strColDesc, $strSeleccion, $bolMultipleS = false) {
    $oConecta = new Conecta();
    if($bolMultipleS) {
      $strConsulta = "SELECT $strColDesc AS valor FROM $strTabla WHERE $strColIndice IN('$strSeleccion')";
    } else {
      $strConsulta = "SELECT $strColDesc AS valor FROM $strTabla WHERE $strColIndice = '$strSeleccion' LIMIT 1";
    }
    $oConecta->Consulta($strConsulta);
    $strRes = "";
    while(!$oConecta->EOF) {
      $strRes .= $oConecta->Field('valor') . ", ";
      $oConecta->MoveNext();
    }
    $strRes = substr($strRes, 0, -2);
    return $strRes;
  }

  /**
   * Obtiene un valor de una tabla, con multiples indices
   * 
   * @param string  $strTabla       Tabla que contiene la informacion
   * @param string  $strColIndices  Nombre de las columnas indice (generalmente el id de la tabla, separados por coma)
   * @param string  $strColDesc     Nombre de la columna que se regresará, puede ser de la forma "columna" o "CONCAT(columna1, columna2)"
   * @param string  $strSelecciones Los indices que se seleccionaran, separados por comas, y en el mismo orden que los indices
   */
  static function GetValueByTableMultipleIndex($strTabla, $strColIndices, $strColDesc, $strSelecciones) {
    $oConecta    = new Conecta();
    $strConsulta = "SELECT $strColDesc AS valor FROM $strTabla WHERE ";
    $arrInd      = explode(",", $strColIndices );
    $arrVals     = explode(",", $strSelecciones);
    foreach($arrInd as $intInd => $strVal) {
      $strConsulta .= "$strVal = '" . ($arrVals[$intInd]) . "' AND ";
    }
    $strConsulta = substr($strConsulta, 0, -4) . "LIMIT 1";
    
    $oConecta->Consulta($strConsulta);
    $strRes = "";
    while(!$oConecta->EOF) {
      $strRes .= $oConecta->Field('valor') . ", ";
      $oConecta->MoveNext();
    }
    $strRes = substr($strRes, 0, -2);
    return $strRes;
  }

  /**
   * Convierte una cadena en un arreglo, y remueve las partes en blanco.
   *
   * Por default se cambian los separadores de linea ("\n")
   * 
   * @param String $strCadena cadena que se expanderá en un array
   * 
   * @return Array
   */
  static function ExplodeNoEmpty($strCadena, $strSeparator = "\n") {
    $arrTmp = explode($strSeparator, $strCadena);
    $arrRegreso = Array();
    foreach($arrTmp as $strLinea) {
      if(trim($strLinea)) {
        $arrRegreso[] = trim($strLinea);
      }
    }

    return $arrRegreso;
  }

  /**
   * Convierte una IP que puede estar en una forma extraña a una forma normal.
   * Actualmente solo sirve para la versión pequeña de localhost (::1)
   * 
   * @param String $strIp La ip a convertir
   *
   * @return String la IP convertida
   */
  static function IpNormal($strIp) {
    if($strIp == '::1') {
      return '127.0.0.1';
    }

    return $strIp;
  }
  
  /**
   * Obtiene la extensión del nombre de un archivo
   * 
   * @param String $strFileName El nombre del archivo
   *
   * @return String La extensión de dicho archivo
   */
  static function FileExt($strFileName) {
    $strFileName = strtolower($strFileName);
    $exts        = @split("[/\\.]", $strFileName);
    $n           = count($exts)-1;
    $exts        = $exts[$n];
    
    return $exts;
  }
  
  /**
   * Funcion que calcula cuantos dias existen entre dos fechas.
   * 
   * @param string $strFechaIni Fecha inicial.
   * @param string $strFechaFin Fecha final. Si no se pasa se toma la fecha actual.
   * 
   * @return int Meses entre las fechas
   * 
   */
  static function DiasDesdeFecha($strFechaIni, $strFechaFin = "") {
    if($strFechaFin == "") {
      $strFechaFin = date("Y-m-d");
    }
    
    $unxFechaIni = strtotime($strFechaIni);
    $unxFechaFin = strtotime($strFechaFin);
    
    $j = 100000;
    for($i = 0; $j > 0; $i++, $j--) {
      if(mktime(0, 0, 0, date("m", $unxFechaIni), date("d", $unxFechaIni) + $i, date("Y", $unxFechaIni)) > $unxFechaFin) {
        return $i - 1;
      }
    }
    return -1;
  }
  
  /**
   * Funcion que calcula cuantos meses existen entre dos fechas.
   * 
   * @param string $strFechaIni Fecha inicial.
   * @param string $strFechaFin Fecha final. Si no se pasa se toma la fecha actual.
   * 
   * @return int Meses entre las fechas
   * 
   */
  static function MesesDesdeFecha($strFechaIni, $strFechaFin = "") {
    if($strFechaFin == "") {
      $strFechaFin = date("Y-m-d");
    }
    
    $unxFechaIni = strtotime($strFechaIni);
    $unxFechaFin = strtotime($strFechaFin);
    
    $j = 10000;
    for($i = 0; $j > 0; $i++, $j--) {
      if(mktime(0, 0, 0, date("m", $unxFechaIni) + $i, date("d", $unxFechaIni), date("Y", $unxFechaIni)) > $unxFechaFin) {
        return $i - 1;
      }
    }
    return -1;
  }
  
  /**
   * Funcion que calcula cuantos años existen entre dos fechas.
   * 
   * @param string $strFechaIni Fecha inicial.
   * @param string $strFechaFin Fecha final. Si no se pasa se toma la fecha actual.
   * 
   * @return int Años entre las fechas
   */
  static function AniosDesdeFecha($strFechaIni, $strFechaFin = "") {
    return intval(Variables::MesesDesdeFecha($strFechaIni, $strFechaFin) / 12);
  }
  
  /**
   * Normaliza una fecha de tipo YYYY-MM-DD, MM-DD-YYYY o DD/MM/YYYY a YYYY-MM-DD
   * 
   * @param string $strFecha Fecha a normalizar
   * @return string Fecha Normalizada
   */
  static function NormalizeDate3($strFecha) {
    $strFecha = Variables::DeepTrim($strFecha);
    if(substr($strFecha, 2, 1) == "/") {
      return Variables::NormalizeDate($strFecha);
    } else {
      return Variables::NormalizeDate2($strFecha);
    }
  }

  /**
   * Convierte una fecha de tipo DD-MM-YYYY o DD/MM/YYYY a YYYY-MM-DD
   * 
   * @param string $strFecha Fecha de tipo DD-MM-YYYY o DD/MM/YYYY
   * 
   * @return string Fecha de tipo YYYY-MM-DD
   */
  static function NormalizeDate2($strFecha) {
    $strFecha = Variables::DeepTrim($strFecha);
    return substr($strFecha, 6, 4) . "-" . substr($strFecha, 3, 2) . "-" . substr($strFecha, 0, 2);
  }

  static function NormalizeDate22($strFecha) {
    $strFecha = Variables::DeepTrim($strFecha);
    return substr($strFecha, 3, 2) . "-" . substr($strFecha, 0, 2) . "-" . substr($strFecha, 6, 4);
  }
  
  /**
   * Intenta obtener el valor de una fecha dada en formato YYYY-MM-DD o en MM-DD-YYYY
   * Si la fecha tiene alguno de los dos formatos, pero no es posible, se trata de obtener la fecha
   * en formato YYYY-DD-MM o en DD-MM-YYYY (Esto es solo un failsafe y NO DEBERÍA de utilizarse así,
   * una fecha en ese formato puede ser confundida con las dos anteriores)
   * 
   * @param String $strFecha la fecha que se checará
   *
   * @return String la fecha en formato YYYY-MM-DD si es posible cambiarla, "" en caso contrario
   */
  static function NormalizeDate($strFecha) {
    $strFecha = Variables::DeepTrim($strFecha);
    if(!preg_match('/^(\d\d\d\d)[-\/](\d\d?)[-\/](\d\d?)$/', $strFecha, $matches)) {
      if(!preg_match('/^(\d\d?)[-\/](\d\d?)[-\/](\d\d\d\d)$/', $strFecha, $matches)) {
        // No parece tener una fecha
        return "";
      } else {
        if(checkdate($matches[1], $matches[2], $matches[3])) {
          return "{$matches[3]}-{$matches[1]}-{$matches[2]}";
        } else if(checkdate($matches[2], $matches[1], $matches[3])) {
          return "{$matches[3]}-{$matches[2]}-{$matches[1]}";
        } else {
          return "";
        }
      }
    } else {
      if(checkdate($matches[2], $matches[3], $matches[1])) {
        return "{$matches[1]}-{$matches[2]}-{$matches[3]}";
      } else if(checkdate($matches[3], $matches[2], $matches[1])) {
        return "{$matches[1]}-{$matches[3]}-{$matches[2]}";
      } else {
        return "";
      }
    }
//    return checkdate($matches[2], $matches[3], $matches[1]);
  }

  /**
   * Función que quita los espacios de una cadena (Incluyendo el escurridiso tipo 160)
   *
   * @param String $strCadena Cadena a la que se quitarán los espacios de las orillas
   * 
   * @return String Cadena sin espacios en las orillas
   */
  static function DeepTrim($strCadena) {
    return trim(str_replace(chr(160), "", $strCadena));
  }

  /**
   * Función que modifica las fechas de un excel.
   * Excel guarda las fechas de una manera bastante extraña (Las guarda con el
   * formato de fecha '1900', que significa que 1 es '1900-01-01', 2 es
   * '1900-01-02', etc)
   *
   * @param String $strDate        Fecha en formato 1900
   * @param int    $intAniosQuitar Cuantos años extra se quitarán (Default 0)
   * 
   * @return Date Fecha que se pasó en formato 'Y-m-d'
   */
  static function DateExcelSaved($strDate, $intAniosQuitar = 0) {
    $strFechaTemp = date("Y-m-d", mktime(0, 0, 0, 1, $strDate, 1900));
    $strFechaAmp  = date("Y-m-d", mktime(0, 0, 0, substr($strFechaTemp, 5, 2), substr($strFechaTemp, 2, 2), substr($strFechaTemp, 8, 2) + (2000 - $intAniosQuitar)));
    return $strFechaAmp;
  }
  
  static function DebugInfo() {
    $strInfoAdicional = '';
    
    $strInfoAdicional .= 'OPTION        : ' . Variables::GetPageOption() . "<br>\n";
    
    if(isset($_SERVER['HTTP_REFERER'   ])) { $strInfoAdicional .= 'REFERER       : ' . $_SERVER['HTTP_REFERER'   ] . "<br>\n"; }
    if(isset($_SERVER['HTTP_USER_AGENT'])) { $strInfoAdicional .= 'USE_AGENT     : ' . $_SERVER['HTTP_USER_AGENT'] . "<br>\n"; }
    if(isset($_SERVER['REMOTE_ADDR'    ])) { $strInfoAdicional .= 'REMOTE_ADDR   : ' . $_SERVER['REMOTE_ADDR'    ] . "<br>\n"; }
    if(isset($_SERVER['QUERY_STRING'   ])) { $strInfoAdicional .= 'QUERY_STRING  : ' . $_SERVER['QUERY_STRING'   ] . "<br>\n"; }
    if(isset($_SERVER['REQUEST_URI'    ])) { $strInfoAdicional .= 'REQUEST_URI   : ' . $_SERVER['REQUEST_URI'    ] . "<br>\n"; }
    if(isset($_SERVER['REQUEST_METHOD' ])) { $strInfoAdicional .= 'REQUEST_METHOD: ' . $_SERVER['REQUEST_METHOD' ] . "<br>\n"; }
    
    return $strInfoAdicional;
  }
  
  /**
   * Función que ayuda a obtener el indice estatico para las columnas alternas
   * 
   * @staticvar int $intNumeroAlterno Numero que se alternará para las columnas
   * 
   * @param     int $intOpcion        Operacion que se realizará: -1 aumenta y regresa el valor; -2 solo regresa el valor; cualquier otro numero pone el valor a ese numero y lo regresa
   * 
   * @return    int Numero alterno actual
   */
  static function __AlternarModifier($intOpcion) {
    static $i = 1;
    switch($intOpcion) {
      case -1:
        return ++$i;
        break;
      case -2:
        return $i;
        break;
      default:
        $i = $intOpcion;
        return $i;
        break;
    }
  }

  static function AlternarConClase() {
    return "class='ColumnasAlternas" . Variables::Alternar() . "'";
  }
  
  static function Alternar($intCuantosRenglones = 2) {
    return Variables::__AlternarModifier(-1) % $intCuantosRenglones;
  }

  /**
   * Función que obtiene la fecha que se imprimirá. Si es una fecha valida, se
   * regresara tal cual; si es una fecha como '0000-00-00 00:00:00', se
   * regresará una cadena vacia
   * 
   * @param date $strFecha la fecha que se quiere imprimir
   * 
   * @return string La fecha que se imprimirá
   */
  static function FechaParaImprimir($strFecha) {
    if(
      ($strFecha == '0000-00-00 00:00:00') ||
      ($strFecha == '0000-01-01 00:00:00') ||
      ($strFecha == '0001-01-01 00:00:00') ||
      !strtotime($strFecha)
    ) {
      return '';
    } else {
      return $strFecha;
    }
  }

  /**
   * Función que cambia una fecha de tipo '1981-09-24 a tipo 24 de Septiembre de 1981'
   * @param date    $strFecha       La fecha que se quiere imprimir
   * @param boolean $bolIncluirHora Si se incluira la hora en la impresion
   */
  static function FechaParaImprimir2($strFecha, $bolFechaCorta = false, $bolIncluirHora = false) {
    $arrMeses = Variables::ArrayMeses(false, false, $bolFechaCorta);
    
    if($bolFechaCorta) {
      $strRes = (substr($strFecha, 8, 2) - 0) .  " "   . $arrMeses[substr($strFecha, 5, 2) - 0] .  " "   . substr($strFecha, 0, 4);
    } else {
      $strRes = (substr($strFecha, 8, 2) - 0) . " de " . $arrMeses[substr($strFecha, 5, 2) - 0] . " de " . substr($strFecha, 0, 4);
    }
    
    if($bolIncluirHora) {
      $strRes .= " ";
      if(substr($strFecha, 11, 2) > 11) {
        if(substr($strFecha, 11, 2) == 12) {
          $strRes .= (substr($strFecha, 11, 2) -  0) . ":" . substr($strFecha, 14, 2) . "PM";
        } else {
          $strRes .= (substr($strFecha, 11, 2) - 12) . ":" . substr($strFecha, 14, 2) . "PM";
        }
      } else {
        $strRes .= " " . substr($strFecha, 11, 5) . "AM";
      }
    }
    
    return $strRes;
  }

  /**
   * Función que obtiene la IP del cliente
   *
   * @return String IP del cliente, en formato AAA.BBB.CCC.DDD
   */
  static function GetRemoteIP() {
    return Variables::IpNormal($_SERVER['REMOTE_ADDR']);
  }

  static function AgregarDiasAFecha($strFecha, $intDias = 1) {
    $strFechaN = Variables::NormalizeDate(substr($strFecha, 0, 10));
    $strAnio = substr($strFechaN, 0, 4);
    $strMes  = substr($strFechaN, 5, 2);
    $strDia  = substr($strFechaN, 8, 2);

    $strFechaRet = date("Y-m-d", mktime(0, 0, 0, $strMes, $strDia + $intDias, $strAnio));

    return $strFechaRet;
  }
  
  static function QuitarAcentos($strRes) {
    $strRes = str_replace(utf8_decode("Á"), "A", $strRes);
    $strRes = str_replace(utf8_decode("É"), "E", $strRes);
    $strRes = str_replace(utf8_decode("Í"), "I", $strRes);
    $strRes = str_replace(utf8_decode("Ó"), "O", $strRes);
    $strRes = str_replace(chr(195) . chr(147), "O", $strRes);
    $strRes = str_replace(utf8_decode("Ú"), "U", $strRes);
    return $strRes;
  }
  
  /**
   * Función que regresa un select para seleccionar un mes
   * 
   * @param  String $strNombreSelect   Nombre que tendrá el select, o '' si no se pondrá la cabecera SELECT y solo se necesitan los options
   * @param  int    $intMesSel         Mes seleccionado (0 para la parte de TODOS LOS MESES)
   * @param  int    $intSeleccionTodos Si se pasa 1, se podrá poner la opción de seleccionar TODOS los meses (para un consolidado o algo así)
   * @return String
   */
  static function SelectMes($strNombreSelect = "", $intMesSel = -1, $intSeleccionTodos = 0) {
    $arrMeses = array();
    $strOpciones = "";
    $arrMeses['0' ] = ":: TODOS ::";
    $arrMeses['01'] = "ENERO"     ;    $arrMeses['02'] = "FEBRERO"   ;    $arrMeses['03'] = "MARZO"     ;    $arrMeses['04'] = "ABRIL"     ;
    $arrMeses['05'] = "MAYO"      ;    $arrMeses['06'] = "JUNIO"     ;    $arrMeses['07'] = "JULIO"     ;    $arrMeses['08'] = "AGOSTO"    ;
    $arrMeses['09'] = "SEPTIEMBRE";    $arrMeses['10'] = "OCTUBRE"   ;    $arrMeses['11'] = "NOVIEMBRE" ;    $arrMeses['12'] = "DICIEMBRE" ;
    
    if($intSeleccionTodos) {
    } else {
      unset($arrMeses['0']);
      if($intMesSel == 0) {
        $intMesSel = -1;
      }
    }
    
    if($intMesSel == -1) {
      $intMesSel = date("m");
    }
    
    foreach($arrMeses as $intIdMes => $strNombreMes) {
      if($intIdMes == $intMesSel) {
        $strSeleccionado = "SELECTED";
      } else {
        $strSeleccionado = "";
      }
      $strOpciones .= "<option value='$intIdMes' $strSeleccionado>$strNombreMes</option>";
    }
    
    if($strNombreSelect != "") {
      $strOpciones = "<select name='$strNombreSelect'>$strOpciones</select>";
    }
    return $strOpciones;
  }
  
  /**
   * Función que regresa un select para seleccionar un año
   * 
   * @param  String $strNombreSelect Nombre que tendrá el select, o '' si no se pondrá la cabecera SELECT y solo se necesitan los options
   * @param  int    $intAnioSel      Año que se mostrará seleccionado (-1 o 0 para el actual)
   * @param  int    $intAniosAntes   Numero de años que se mostraran antes y despues del actual (-1 para el valor default, 10); Si es un arreglo con dos valores, contendrá el primer y ultimo año a mostrar
   * @return String
   */
  static function SelectAnio($strNombreSelect = "", $intAnioSel = -1, $intAniosAntes = -1) {
    $intAnioAct = date("Y");
    
    if($intAniosAntes == -1) {
      $intAniosAntes = 10;
    }
    
    if(($intAnioSel == -1) || ($intAnioSel == 0)) {
      $intAnioSel = $intAnioAct;
    }
    
    if(is_array($intAniosAntes)) {
      $intPrimerAnio = $intAniosAntes[0];
      $intUltimoAnio = $intAniosAntes[1];
    } else {
      $intPrimerAnio = $intAnioAct - $intAniosAntes;
      $intUltimoAnio = $intAnioAct + $intAniosAntes;
    }
    
    $strOpciones = "";
    for($i = $intPrimerAnio; $i <= $intUltimoAnio; $i++) {
      if($i == $intAnioSel) {
        $strSeleccionado = "SELECTED";
      } else {
        $strSeleccionado = "";
      }
      $strOpciones .= "<option value='$i' $strSeleccionado>$i</option>";
    }
    
    if($strNombreSelect != "") {
      $strOpciones = "<select name='$strNombreSelect'>$strOpciones</select>";
    }
    
    return $strOpciones;
  }
  
  /**
   * Función que sincroniza los indices, esto es, pone el dato en el nombre del 
   * indice, como sigue:
   * 
   * $arrIndices[] = "Ajale";
   * $arrIndices[] = "Jalea";
   * 
   * se convertirá en:
   * 
   * $arrIndices["Ajale"] = "Ajale";
   * $arrIndices["Jalea"] = "Jalea";
   * 
   * @param  array $arrDatos arreglo de una dimension que será sincronizado
   * @return array 
   */
  static function ArrayIndexSync($arrDatos) {
    $arrRet = array();
    foreach($arrDatos as $strDato) {
      $arrRet[$strDato] = $strDato;
    }
    
    return $arrRet;
  }
  
  /**
   * Función que convierte un arreglo no indexado en un arreglo indexado con 
   * los nombres de los indices en los datos; esto es:
   * 
   * $arrIndices[] = "A=Ajale";
   * $arrIndices[] = "B=Jalea";
   *
   * se convierte en:
   * $arrIndices["A"] = "Ajale";
   * $arrIndices["B"] = "Jalea";
   * 
   * Si la linea no contiene el delimitador, toda la linea se convierte en 
   * indice Y en contenido.
   * 
   * @param  array  $arrDatos       Arreglo con los datos fuente
   * @param  string $strDelimitador Delimitador
   * @return array 
   */
  static function ArrayIndexExplode($arrDatos, $strDelimitador = "=") {
    $arrRet = array();
    
    foreach($arrDatos as $strDato) {
      $arrTmp = explode($strDelimitador, $strDato, 2);
      if(count($arrTmp) == 1) {
        $strIndice = $strDatoF = $strDato;
      } else {
        $strIndice = $arrTmp[0];
        $strDatoF  = $arrTmp[1];
      }
      $arrRet[$strIndice] = $strDatoF;
    }
    
    return $arrRet;
  }
  
  /**
   * Funcion que capitaliza una variable de la siguiente manera
   * 
   * ajale_jalea_jalalai => AjaleJaleaJalalai
   * 
   * Si se pone un prefijo, este se pondrá antes de la variable sin modificar, como en
   * ajale_jalea_jalalai, int => intAjaleJaleaJalalai
   * 
   * @param type $strNombre Nombre de la variable
   * @param type $strPrefix Prefijo que llevará la variable
   * 
   */
  static function CapitalizeVar($strNombre, $strPrefix = "") {
    $strRes = $strPrefix;
    $arrCompNom = explode("_", $strNombre);
    foreach($arrCompNom as $strParteNom) {
      $strRes .= strtoupper(substr($strParteNom, 0, 1)) . strtolower(substr($strParteNom, 1));
    }
    
    return $strRes;
  }
  
  /**
   * Arreglo que obtiene los meses para ser usados en maneras distintas
   * 
   * @param boolean $bolConPadding    Si se pondrá un padding (un extra 0) en los indices de los meses
   * @param boolean $bolIncluirTodos  Si se incluirá el mes 0 (TODOS)
   * @param boolean $bolSoloIniciales Si solo se pondran las tres primeras letras de cada mes
   * @param boolean $bolCapitalizar   Si se capitalizará (Esto es, primer letra mayuscula, las dmeas minusculas) los nombres de los meses
   * @return Array                    Arreglo con los meses
   */
  static function ArrayMeses($bolConPadding = false, $bolIncluirTodos = false, $bolSoloIniciales = false, $bolCapitalizar = false) {
    $arrMeses  = array();
    $arrMesesT = array();
    
    $arrMeses['00'] = ":: TODOS ::";
    $arrMeses['01'] = "ENERO"     ;    $arrMeses['02'] = "FEBRERO"   ;    $arrMeses['03'] = "MARZO"     ;    $arrMeses['04'] = "ABRIL"     ;
    $arrMeses['05'] = "MAYO"      ;    $arrMeses['06'] = "JUNIO"     ;    $arrMeses['07'] = "JULIO"     ;    $arrMeses['08'] = "AGOSTO"    ;
    $arrMeses['09'] = "SEPTIEMBRE";    $arrMeses['10'] = "OCTUBRE"   ;    $arrMeses['11'] = "NOVIEMBRE" ;    $arrMeses['12'] = "DICIEMBRE" ;
    
    if(!$bolIncluirTodos) {
      unset($arrMeses['00']);
    }
    
    if(!$bolConPadding) {
      foreach($arrMeses as $intMes => $strMes) {
        $arrMesesT[$intMes - 0] = $strMes;
      }
      $arrMeses = $arrMesesT;
      unset($arrMesesT);
    }
    
    if($bolSoloIniciales) {
      foreach($arrMeses as $intMes => $strMes) {
        $arrMesesT[$intMes] = substr($strMes, 0, 3);
      }
      $arrMeses = $arrMesesT;
      unset($arrMesesT);
    }
    
    if($bolCapitalizar) {
      foreach($arrMeses as $intMes => $strMes) {
        $arrMesesT[$intMes] = ucfirst(strtolower($strMes));
      }
      $arrMeses = $arrMesesT;
      unset($arrMesesT);
    }
    
    return $arrMeses;
  }
  
  static function GetDirContent($strDir = ".", $bolOmitirDirectorios = false) {
    $arrRes = array();
    if ($handle = opendir($strDir)) {
      while(false !== ($entry = readdir($handle))) {
        if(substr($entry, 0, 1) != '.') {
          if(($bolOmitirDirectorios) && (filetype("$strDir/$entry") == "dir")) {
            continue;
          }
          $arrRes[$entry] = $entry;
        }
      }
      closedir($handle);
    }
    return $arrRes;
  }
  
  /**
   * Función que copia un valor de un arreglo a otra parte de si mismo.
   * 
   * Esta función puede copiar varios valores a la vez. Tambien puede copiar a 
   * varios lugares a la vez, o sumar varios valores (Los cuales serán tratados 
   * como numeros) dependiendo del estado de las variables de ingreso:
   * 
   * Si $varDe es un arreglo y $varA es un arreglo, los valores de 
   * $arrDatos[$arrDe[$i]] seran puestos en $arrDatos[$arrA[$i]], para cada 
   * elemento $i en $arrDe
   * 
   * Si $varDe es un arreglo y $varA es una cadena de texto, los valores de cada 
   * indice en $varDe seran sumados, y la suma será puesta en $arrDatos[$varA]
   * 
   * Si $varDe es una cadena de texto y $varA es un arreglo, los valores de 
   * $arrDatos[$varDe] seran copiados a cada elemento en $varA 
   * ($arrDatos[(cada_elemento_de_varA)]
   * 
   * Si $varDe es una cadena de texto y $varA es una cadena de texto, los 
   * valores seran copiados desde $arrDatos[$arrDe] a $arrDatos[$varA]
   * 
   * Si $bolRecursivo es verdadero, se tomará $arrDatos como que contiene una coleccion de arreglos, a cada uno de los cuales se les hará la operación que se indica anteriormente
   * 
   * @param  array   $arrDatos     Arreglo con los datos fuentes.
   * @param  array   $varA         Variable que guarda el indice (o los indices) a donde seran copiados los valores
   * @param  array   $varDe        Variable que guarda el indice (o los indices) desde donde se copiaran los valores
   * @param  boolean $bolRecursivo Si es verdadero, el arreglo se tomará como que es una coleccion de arreglos
   * 
   * @return array El arreglo con los nuevos valores
   */
  static function CopiarValores($arrDatos, $varA, $varDe, $bolRecursivo = false) {
    if($bolRecursivo) {
      $arrDatosReg = array();
      foreach($arrDatos as $strIndice => $arrValores) {
        $arrDatosReg[$strIndice] = Variables::CopiarValores($arrValores, $varA, $varDe);
      }
      return $arrDatosReg;
    }
    
    if(is_array($varDe) && is_array($varA)) {
      // Los dos son arreglos: se copia cada elemento a cada destino
      foreach($varDe as $intIndice => $strindiceDesde) {
        $arrDatos[$varA[$intIndice]] = $arrDatos[$strindiceDesde];
      }
    } else if(is_array($varDe) && !is_array($varA)) {
      // Fuente es arreglo y destino es cadena de texto: se suman los valores y se colocan en el destino
      $fltSuma = 0;
      foreach($varDe as $strIndice) {
        $fltSuma += $arrDatos[$strIndice];
      }
      $arrDatos[$varA] = $fltSuma;
    } else if(!is_array($varDe) && is_array($varA)) {
      // Fuente es cadena de texto, destino es arreglo: se copia a todos los indices destino
      foreach($varA as $strIndice) {
        $arrDatos[$strIndice] = $arrDatos[$varDe];
      }
    } else {
      // Ambos cadena de texto: copia
      $arrDatos[$varA] = $arrDatos[$varDe];
    }
    
    return $arrDatos;
  }
  
  /**
   * Concatenar los valores en un arreglo con cadenas dadas
   *
   * @param array   $arrDatos         Arreglo con los datos base
   * @param string  $strConcatenarPre Cadena que se concatenará al principio
   * @param string  $strConcatenarPos Cadena que se concatenará al final
   * @param boolean $bolRecursivo     Si es verdadero, el arreglo es una coleccion de arreglos en donde los otros elementos se concatenaran
   * 
   * @return array Arreglo con los valores multiplicados
   */
  static function ConcatenarValores($arrDatos, $strConcatenarPre = "", $strConcatenarPos = "", $bolRecursivo = false) {
    if($bolRecursivo) {
      $arrDatosReg = array();
      foreach($arrDatos as $strIndice2 => $arrValores) {
        $arrDatosReg[$strIndice2] = Variables::ConcatenarValores($arrValores, $strConcatenarPre, $strConcatenarPos);
      }
      return $arrDatosReg;
    }
    
    foreach($arrDatos as $strIndice => $strValor) {
      $arrDatos[$strIndice] = $strConcatenarPre . $strValor . $strConcatenarPos;
    }
    
    return $arrDatos;
  }
  
  /**
   * Multiplica los valores en un arreglo por un factor dado
   * 
   * @param array   $arrDatos     Arreglo con los datosbase
   * @param string  $strIndice    Indice al que se le hará la multiplicacion
   * @param float   $fltFactor    Factor por el que se multiplicara
   * @param boolean $bolRecursivo Si es verdadero, el arreglo es una coleccion de arreglos en donde los otros elementos se multiplicaran
   * 
   * @return array Arreglo con los valores multiplicados
   */
  static function MultiplicarValorEnArreglo($arrDatos, $strIndice, $fltFactor, $bolRecursivo = false) {
    if($bolRecursivo) {
      $arrDatosReg = array();
      foreach($arrDatos as $strIndice2 => $arrValores) {
        $arrDatosReg[$strIndice2] = Variables::MultiplicarValorEnArreglo($arrValores, $strIndice, $fltFactor);
      }
      return $arrDatosReg;
    }
    
    $arrDatos[$strIndice] *= $fltFactor;
    
    return $arrDatos;
  }
  
  /**
   * Quita todos los elementos de un arreglo con excepcion de los que se presentan 
   * en $arrElementosGuardar.
   * 
   * Si dichos elementos no existen, se ponen como vacio.
   * 
   * @param type $arrDatos             Arreglo con los datos base
   * @param type $arrElementosAGuardar Arreglo con los datos que se van a quedar
   * @param type $bolRecursivo         Si es verdadero, el arreglo es una coleccion de arreglos en donde los otros elementos se quitaran
   * 
   * @return array Arreglo con los elementos no necesarios quitados
   */
  static function QuitarTodosMenos($arrDatos, $arrElementosAGuardar, $bolRecursivo = false) {
    $arrDatosReg = array();
    if($bolRecursivo) {
      foreach($arrDatos as $strIndice => $arrValores) {
        $arrDatosReg[$strIndice] = Variables::QuitarTodosMenos($arrValores, $arrElementosAGuardar);
      }
    } else {
      foreach($arrElementosAGuardar as $strIndice) {
        $arrDatosReg[$strIndice] = $arrDatos[$strIndice];
      }
    }
    
    return $arrDatosReg;
  }
  
  /**
   * Quita todos los elementos de un arreglo con excepcion de los que se presentan 
   * en $arrElementosGuardar.
   * 
   * Si dichos elementos no existen, se ponen como vacio.
   * 
   * @param type $arrDatos            Arreglo con los datos base
   * @param type $arrElementosAQuitar Arreglo con los datos que se van a quitar. Si es una cadena solo se quitara dicho elemento
   * @param type $bolRecursivo        Si es verdadero, el arreglo es una coleccion de arreglos en donde los otros elementos se quitaran
   * 
   * @return array Arreglo con los elementos no necesarios quitados
   */
  static function QuitarTodos($arrDatos, $arrElementosAQuitar, $bolRecursivo = false) {
    $arrDatosReg = array();
    if(!is_array($arrElementosAQuitar)) {
      $arrElementosAQuitar = array($arrElementosAQuitar);
    }
    if($bolRecursivo) {
      foreach($arrDatos as $strIndice => $arrValores) {
        $arrDatosReg[$strIndice] = Variables::QuitarTodos($arrValores, $arrElementosAQuitar);
      }
    } else {
      foreach($arrElementosAQuitar as $strIndice) {
        unset($arrDatos[$strIndice]);
      }
      $arrDatosReg = $arrDatos;
    }
    
    return $arrDatosReg;
  }
  
  /**
   * Función que ordena el contenido de un arreglo por la clave en cada uno de 
   * los subarreglos.
   * 
   * Si alguna clave se repite, será reemplazada.
   * 
   * Si se especifica el arreglo $arrIndicesBase, el arreglo regresado contendrá 
   * esos datos, en ese orden. Si no existe un dato, se agregará como vacio.
   * 
   * @param array  $arrDatos Arreglo con los datos a ordenar
   * @param string $strClave Clave por la cual se ordenará el arreglo
   * @return array           El arreglo ordenado
   */
  static function OrdenarPorClave($arrDatos, $strClave, $arrIndicesBase = "") {
    $arrDatosReg = array();
    
    foreach($arrDatos as $arrD1) {
      $arrDatosReg[$arrD1[$strClave]] = $arrD1;
    }
    
    if(is_array($arrIndicesBase)) {
      $arrDatos2 = array();
      foreach($arrIndicesBase as $strIndice) {
        if(isset($arrDatosReg[$strIndice])) {
          $arrDatos2[$strIndice] = $arrDatosReg[$strIndice];
        } else {
          $arrDatos2[$strIndice] = array();
        }
      }
      
      return $arrDatos2;
    } else {
      return $arrDatosReg;
    }
    
    return $arrDatosReg;
  }
  
  /**
   * Función que ordena el contenido de un arreglo por la clave en cada uno de 
   * los subarreglos SIN quitar el nombre del indice y SIN reemplazar los repetidos
   * 
   * $strClave debe de referirse a una clave numerica entera
   * 
   * @param array  $arrDatos Arreglo con los datos a ordenar
   * @param string $strClave Clave por la cual se ordenará el arreglo
   */
  static function OrdenarPorClaveConservaIndice($arrDatos, $strClave) {
    $arrDatosReg = array();
    
    // Primero obtener el minimo y maximo
    $intMinVal = $intMaxVal = false;
    foreach($arrDatos as $arrD1) {
      if($intMinVal === false) {
        // Es el primer valor a checar, de aqui salen los primero indices mayor y menor
        $intMinVal = $intMaxVal = $arrD1[$strClave];
      } else {
        if($intMinVal > $arrD1[$strClave]) $intMinVal = $arrD1[$strClave];
        if($intMaxVal < $arrD1[$strClave]) $intMaxVal = $arrD1[$strClave];
      }
    }
    
    // Ahora empezamos desde el valor menor hasta el mayor
    for($i = $intMinVal; $i <= $intMaxVal; $i++) {
      foreach($arrDatos as $strNomInd => $arrD1) {
        if($arrD1[$strClave] == $i) {
          $arrDatosReg[$strNomInd] = $arrD1;
          unset($arrDatos[$strNomInd]);
        }
      }
    }
    
    return $arrDatosReg;
  }
  
  /**
   * Función que quita los espacios repetidos de una cadena (esto es, reemplaza 
   * los "  " con " " recursivamente hasta que ya no existen "  "). Esta función
   * primero utiliza un TRIM, por lo que los saltoa de linea y los tabs al 
   * principio y al final de la cadena tambien seran quitados.
   * 
   * @param string $strCadena la cadena con los espacios repetidos
   * 
   * @return string La cadena sin los espacios repetidos
   */
  static function QuitarEspaciosRepetidos($strCadena) {
    // Primero quitar los espacios del principio y del final. Esto tambien quita los saltos de linea y los tabs
    $strCadena = trim($strCadena);
    $strCadena = str_replace("\n", "", $strCadena);
    
    while(true) {
      if(!strpos($strCadena, "  ")) {
        break;
      }
      $strCadena = str_replace("  ", " ", $strCadena);
    }
    
    return $strCadena;
  }
  
  /**
   * Funcion que regresa un numero con formato para impresion. Esto es, se le 
   * agregan las comas para dividir miles y el punto decimal (con dos digitos)
   * 
   * @param float   $fltNumero   Numero a formatear
   * @param boolean $bolEsentero Si es true, el numero es entero y no se imprime punto decimal ni decimales
   * 
   * @return string Numero formateado
   */
  static function FormatoNumero($fltNumero, $bolEsentero = false) {
    if(!$bolEsentero) {
      $strRes = number_format($fltNumero, 2, '.', ',');
    } else {
      $strRes = number_format($fltNumero, 0, '.', ',');
    }
    
    return $strRes;
  }
  
  /**
   * Funcion que regresa un numero para imprimir con formato de moneda.
   * 
   * @param float $fltNumero Numero a formatear
   * @return string Numero con formato
   */
  static function FormatoCurrency($fltNumero) {
    return '$' . Variables::FormatoNumero($fltNumero);
  }
  
  /**
   * Funcion que agrega un numro de meses a un mes de un año especifico.
   * 
   * Puede quitar meses si el parametro $intMesesAgregar es negativo.
   * 
   * Regresa un arreglo con la parte axo con el año, y mes con el mes.
   * 
   * 
   * @param  int   $intAnio         Año de la fecha a modificar
   * @param  int   $intMes          Mes al que se le agregaran los meses
   * @param  int   $intMesesAgregar Cantidad de meses a agregar
   * 
   * @return array Arreglo con los datos de la nueva fecha
   */
  static function AgregarMeses($intAnio, $intMes, $intMesesAgregar) {
    $strNuevaFecha = date("Y-m", mktime(0, 0, 0, $intMes + $intMesesAgregar, 1, $intAnio));
    $arrPartes = explode("-", $strNuevaFecha);
    
    return array("axo" => $arrPartes[0], "mes" => $arrPartes[1]);
  }
  
  /**
   * Función que obtiene los parametros dentro de delimitadores de una cadena.
   * 
   * Esto es: pasa una cadena de tipo
   * "[Ajale][Jalea]aaaa[Jalalai]
   * 
   * a un arreglo que contiene
   * $arr[] = Ajale;
   * $arr[] = Jalea;
   * $arr[] = Jalalai;
   * 
   * Si los delimitadores son iguales, se debe tener cuidado, ya que la cadena
   * 
   * Ajale|Jalea|Jalalai|Nomanches
   * 
   * Solo obtiene
   * $arr[] = Jalea
   * 
   * y la cadena
   * Ajale|Jalea|Jalalai|Nomanches|
   * $arr[] = Jalea
   * $arr[] = Nomanches
   * 
   * Para dividirla por los separadores, mejor usar explode. 
   * La cadena debería de estar así para ser dividida correctamente:
   * |Ajale||Jalea||Jalalai||Nomanches|
   * 
   * @param string $strCadena             La cadena que se dividirá
   * @param string $strDelimitadorInicial El delimitador inicial
   * @param string $strDelimitadorFinal   El delimitador final
   * 
   * @return array Arreglo con las cadenas encontradas
   */
  static function ObtenerCadenasDelimitadas($strCadena, $strDelimitadorInicial = "|", $strDelimitadorFinal = "|") {
    $intIndiceArreglo = 0;
    $intEstadoActual  = 0;
    $strLineaG        = "";
    $arrRet = array();
    
    for($i = 0; $i < strlen($strCadena); $i++) {
      switch($intEstadoActual) {
        case 0:
          // Continuar hasta encontrar la coincidencia del delimitador inicial
          if(substr($strCadena, $i, strlen($strDelimitadorInicial)) == $strDelimitadorInicial) {
            // Encontrada: movernos al siguiente estado
            $intEstadoActual = 1;
            // Aumentar el contador las posiciones necesarias, por si el delimitador es de mas de un caracter
            $i += strlen($strDelimitadorInicial) - 1;
            // Limpiar la cadena antes de pasar al siguiente estado
            $strLineaG = "";
          }
          break;
        case 1:
          // Todo lo encontrado aqui es de la cadena que se incluirá en el arreglo
          if(substr($strCadena, $i, strlen($strDelimitadorFinal)) == $strDelimitadorFinal) {
            // Llegamos al final, cortar la cadena y reiniciar la busqueda
            $arrRet[] = $strLineaG;
            $intEstadoActual = 0;
            // Aumentar el contador las posiciones necesarias, por si el delimitador es de mas de un caracter
            $i += strlen($strDelimitadorFinal) - 1;
          } else {
            $strLineaG .= substr($strCadena, $i, 1);
          }
          break;
      }
    }
    
    return $arrRet;
  }
  
  /**
   * Funcion que quita los espacios anteriores y posteriores a cada uno de los 
   * elementos en un arreglo
   * 
   * @param array   $arrDatos                 Arreglo con los datos a trimear
   * @param string  $strCaracteresAQuitar     Caracteres a quitar del principio y final de cada cadena. Si se manda vacio, se quitaran espacios
   * @param boolean $bolTambienTrimearIndices Verdadero si los indices tambien seran pasados por trim
   * 
   * @return array Arreglo con los datos Trimeados
   */
  static function ArrayTrim($arrDatos, $strCaracteresAQuitar = "", $bolTambienTrimearIndices = false) {
    $arrRet = array();
    foreach($arrDatos as $strIndice => $strLinea) {
      if($bolTambienTrimearIndices) {
        if($strCaracteresAQuitar == "") {
          $strIndice = trim($strIndice);
        } else {
          $strIndice = trim($strIndice, $strCaracteresAQuitar);
        }
      }
      
      if($strCaracteresAQuitar == "") {
        $arrRet[$strIndice] = trim($strLinea);
      } else {
        $arrRet[$strIndice] = trim($strLinea, $strCaracteresAQuitar);
      }
    }
    
    return $arrRet;
  }
  
  /**
   * Función que regresa un color, en hexadecimal, que se encuentra cierta
   * distancia entre dos colores dados. Esto es:
   * 
   * Si tengo el color inicial #FFFFFF, el color final #000000, y quiero dividir
   * el gradiente en 10 piezas, y tomar el color de la tercer pieza, llamaria
   * a la función con los parametros
   * "FFFFFF", "000000", 10, 3
   * 
   * Tomar en cuenta que el menor numero de pasos es 2.
   * Los pasos van de 1 a $intTotalPasos
   * El color puede empezar o no con un signo de gato. Si el color empieza con
   * uno, se regresará uno tambien en la respuesta. Si no es así, solo se 
   * regresará el valor del color
   * 
   * @param string $strHexIni     Color inicial hexadecimal (XXYYZZ o #XXYYZZ)
   * @param string $strHexFin     Color final   hexadecimal (XXYYZZ o #XXYYZZ)
   * @param int    $intTotalPasos Numero de divisiones que se harán en el gradient
   * @param int    $intPasoAct    Paso del que se obtendrá el color resultado
   * @param string $strTipoOp     Tipo de operación que se aplicará entre los colores (Por default ADD)
   * 
   * @return string Color resultado
   */
  static function BlendColorHex($strHexIni, $strHexFin, $intTotalPasos, $intPasoAct, $strTipoOp = "ADD") {
    if(substr($strHexIni, 0, 1) == "#") {
      $bolConGato    = true;
      $arrColoresIni = array(base_convert(substr($strHexIni, 1, 2), 16, 10), base_convert(substr($strHexIni, 3, 2), 16, 10), base_convert(substr($strHexIni, 5, 2), 16, 10));
      $arrColoresFin = array(base_convert(substr($strHexFin, 1, 2), 16, 10), base_convert(substr($strHexFin, 3, 2), 16, 10), base_convert(substr($strHexFin, 5, 2), 16, 10));
    } else {
      $bolConGato    = false;
      $arrColoresIni = array(base_convert(substr($strHexIni, 0, 2), 16, 10), base_convert(substr($strHexIni, 2, 2), 16, 10), base_convert(substr($strHexIni, 4, 2), 16, 10));
      $arrColoresFin = array(base_convert(substr($strHexFin, 0, 2), 16, 10), base_convert(substr($strHexFin, 2, 2), 16, 10), base_convert(substr($strHexFin, 4, 2), 16, 10));
    }
    
    $arrColoresRes = Variables::BlendColorArr($arrColoresIni, $arrColoresFin, $intTotalPasos, $intPasoAct, $strTipoOp);
    
    $strRes = sprintf("%02X%02X%02X", $arrColoresRes[0], $arrColoresRes[1], $arrColoresRes[2]);
    
    if($bolConGato) {
      $strRes = "#" . $strRes;
    }
    
    return $strRes;
  }
  
  /**
   * Función que regresa un color, en arreglo de datos, que se encuentra cierta
   * distancia entre dos colores dados. Esto es:
   * 
   * Si tengo el color inicial array(255, 255, 255), el color final array(0, 0, 0), 
   * y quiero dividir el gradiente en 10 piezas, y tomar el color de la tercer 
   * pieza, llamaria a la función con los parametros
   * array(255, 255, 255), array(0, 0, 0), 10, 3
   * 
   * Tomar en cuenta que el menor numero de pasos es 2.
   * Los pasos van de 1 a $intTotalPasos.
   * Si el arreglo tiene los indices "R, G, B" (en mayusculas), se tomarán esos 
   * como los colores, si no es así se tomaran como 0, 1 y 2 respectivamente.
   * Si el arreglo indica los indices como "R, G, B", el arreglo resultante 
   * tambie los indicará; de otra manera se regresarán con los indices 0, 1, 2
   * 
   * @param array  $arrColorIni   Arreglo con los colores primarios iniciales
   * @param array  $arrColorFin   Arreglo con los colores primarios finales
   * @param int    $intTotalPasos Numero de divisiones que se harán en el gradient
   * @param int    $intPasoAct    Paso del que se obtendrá el color resultado
   * @param string $strTipoOp     Tipo de operación que se aplicará entre los colores (Por default ADD)
   * 
   * return array Arreglo con los valores del color resultante
   */
  static function BlendColorArr($arrColorIni, $arrColorFin, $intTotalPasos, $intPasoAct, $strTipoOp = "ADD") {
    $intTotalPasos -= 0;
    $intPasoAct    -= 0;
    
    if(isset($arrColorIni["R"]) && isset($arrColorIni["G"]) && isset($arrColorIni["B"])) {
      $bolIndicesDados  = true ;
      
      $intColorRedIni   = $arrColorIni["R"] - 0;
      $intColorGreenIni = $arrColorIni["G"] - 0;
      $intColorBlueIni  = $arrColorIni["B"] - 0;
      
      $intColorRedFin   = $arrColorFin["R"] - 0;
      $intColorGreenFin = $arrColorFin["G"] - 0;
      $intColorBlueFin  = $arrColorFin["B"] - 0;
    } else {
      $bolIndicesDados = false;
      
      $intColorRedIni   = $arrColorIni[0] - 0;
      $intColorGreenIni = $arrColorIni[1] - 0;
      $intColorBlueIni  = $arrColorIni[2] - 0;
      
      $intColorRedFin   = $arrColorFin[0] - 0;
      $intColorGreenFin = $arrColorFin[1] - 0;
      $intColorBlueFin  = $arrColorFin[2] - 0;
    }
    
    switch($strTipoOp) {
      case "ADD":
      default:
        $strNombreFunc = "BlendColorParamADD";
    }
    
    $intColorRedRes   = 0;
    $intColorGreenRes = 0;
    $intColorBlueRes  = 0;
    
    eval("\$intColorRedRes   = Variables::$strNombreFunc({$intColorRedIni  }, {$intColorRedFin  }, $intTotalPasos, $intPasoAct);");
    eval("\$intColorGreenRes = Variables::$strNombreFunc({$intColorGreenIni}, {$intColorGreenFin}, $intTotalPasos, $intPasoAct);");
    eval("\$intColorBlueRes  = Variables::$strNombreFunc({$intColorBlueIni }, {$intColorBlueFin }, $intTotalPasos, $intPasoAct);");
    
    if($bolIndicesDados) {
      return array("R" => $intColorRedRes, "G" => $intColorGreenRes, "B" => $intColorBlueRes);
    } else {
      return array($intColorRedRes, $intColorGreenRes, $intColorBlueRes);
    }
  }
  
  /**
   * Función que regresa un color primario que se encuentra a cierta distancia 
   * entre dos colores primarios dados. Esto es:
   * 
   * Si tengo el color inicial 255, el color final 0, y quiero dividir el 
   * gradiente en 10 piezas, y tomar el color de la tercer pieza, llamaria a la 
   * función con los parametros
   * 255, 0, 10, 3
   * 
   * Esta función regresa el valor ADD
   * 
   * Tomar en cuenta que el menor numero de pasos es 2.
   * Los pasos van de 1 a $intTotalPasos.
   * El color debe estar en el rango 0 a 255 para ser un color correcto, sin
   * embargo esta función no lo comprueba
   * 
   * @param int $intColorIni   Valor del color primario inicial
   * @param int $intColorFin   Valor del color primario final
   * @param int $intTotalPasos Numero de divisiones que se harán en el gradient
   * @param int $intPasoAct    Paso del que se obtendrá el color resultado
   * 
   * return int Valor del color primario resultante
   */
  static function BlendColorParamADD($intColorIni, $intColorFin, $intTotalPasos, $intPasoAct) {
    $intTotalPasos -= 1;
    $intPasoAct    -= 1;
    $intColorRes = round((($intColorIni * (2 - ((2 / $intTotalPasos) * $intPasoAct))) + ($intColorFin * (0 + ((2 / $intTotalPasos) * $intPasoAct)))) / 2);
    return $intColorRes;
  }
  
  /**
   * Funcion que pinta un backtrace desde donde se mande llamar la funcion.
   * 
   * Generalmente mata al script al ser llamada.
   * 
   * @param string  $strTituloError       El titulo que se mostrara en la cabecera del reporte
   * @param boolean $bolDie               Si despues de pintar la informacion se morira el srcript
   * @param boolean $bolAchicarParametros Si se necesita que los parametros no sean pintados completos, sino solo un resumen
   */
  static function DebugBacktrace($strTituloError = "", $bolDie = true, $bolAchicarParametros = true) {
    $arrBT = debug_backtrace(false);
    unset($arrBT[0]);
    
    $strRes  = "";
    
    if($strTituloError) {
      $strRes .= "<h1>$strTituloError</h1>";
    }
    
    $strRes .= "<pre>";
    foreach($arrBT as $intIndice => $arrDatosBT) {
      $strRes .= $arrDatosBT["file"] . "(" . $arrDatosBT["line"] . ")\n";
      if(isset($arrDatosBT["type"]) && ($arrDatosBT["type"])) {
        $strRes .= $arrDatosBT["class"] . $arrDatosBT["type"];
      }
      $strRes .= $arrDatosBT["function"];
      if(count($arrDatosBT["args"]) == 0) {
        $strRes .= "( );";
      } else {
        $strRes .= "(\n";
        foreach($arrDatosBT["args"] as $varArgumento) {
          if(is_array($varArgumento)) {
            $varArgumento = "[Array]";
          } else if($varArgumento === false) {
            $varArgumento = "[bol_false]";
          } else if($varArgumento === true) {
            $varArgumento = "[bol_true]";
          } else if($bolAchicarParametros && (strlen($varArgumento) > 70)) {
            $varArgumento = "[" . substr($varArgumento, 0, 70) . "...]";
          } else if(is_object($varArgumento)) {
            $strRes .= "  Objeto[" . get_class($varArgumento) . "]\n";
          } else if(strlen($varArgumento == 0)) {
            $strRes .= "  [Cadena Vacia]\n";
          } else {
            $strRes .= "  $varArgumento\n";
          }
        }
        
//        $strRes  = substr($strRes, 0, -2);
        $strRes .= ");";
      }
      $strRes .= "\n\n";
    }
    $strRes .= "</pre>";
    
    echo $strRes;
    if($bolDie) {
      die;
    }
  }
  
  /**
   * Funcion que regresa el tiempo transcurrido entre dos llamadas a esta funcion
   * 
   * @param  boolean $bolIniciar       Verdadero si se va a iniciar el contador; Falso si se requiere el tiempo
   * @param  string  $strIdentificador Identificador del contador de lapsos (Para poder hacer mas de un lapso a la vez)
   * 
   * @return float Tiempo transcurrido (En microsegundos)
   */
  static function SmallTimeLap($bolIniciar = false, $strIdentificador = "default") {
    static $arrTiempoInicial;
    
    if($bolIniciar) {
      $arrTiempoInicial[$strIdentificador] = microtime(true);
      return 0;
    } else {
      return microtime(true) - $arrTiempoInicial[$strIdentificador];
    }
  }
  
  /**
   * Funcion que actua como un timer para ver los tiempos de ejecución.
   * 
   * Esta función está pensada para debug, ya que imprime directamente los
   * tiempos.
   * 
   * @param string  $strNombreLap Nombre que se le asigna a este LAP para distinguirlo de los anteriores
   * @param boolean $bolSoloLap   Verdadero si solo se requiere el tiempo de este LAP; falso si se requiere el tiempo desde el comienzo hasta ahora
   */
  static function Timer($strNombreLap = "", $bolSoloLap = true) {
    static $arrTiempos      = array();
    static $intIndiceTiempo = 0;
    
    $arrTiempos[$intIndiceTiempo] = microtime(true);
    echo "<pre>";
    if($intIndiceTiempo == 0) {
      echo "-- Iniciando lapsos";
    } else {
      $strAntes = "-- -- Lap $intIndiceTiempo" . ($strNombreLap ? " ($strNombreLap)" : "") . ": ";
      echo str_pad($strAntes, 50) . sprintf("%0.8f", ($arrTiempos[$intIndiceTiempo] - $arrTiempos[$intIndiceTiempo - 1]));
      if(!$bolSoloLap) {
        echo str_pad("\n\n-- Tiempo total: ", 50) . ($arrTiempos[$intIndiceTiempo] - $arrTiempos[0]);
      }
    }
    echo "</pre>\n";
    $intIndiceTiempo++;
  }
  
  static function ExcelHeaders($strNombreArchivo = "") {
    header('Content-type: application/ms-excel');
    if($strNombreArchivo) {
      header('Content-Disposition: attachment; filename=' . $strNombreArchivo);
    }
  }
  
  static function RecursiveCastToArray($arrObjeto) {
    if(is_object($arrObjeto)) {
      $arrObjeto = (array) $arrObjeto;
    }
    if(is_array($arrObjeto)) {
      $arrRet = array();
      foreach($arrObjeto as $strIndice => $varInfo) {
        if(is_object($varInfo)) {
          $varInfo = (array) $varInfo;
        }
        if(is_array($varInfo)) {
          $arrRet[$strIndice] = Variables::RecursiveCastToArray($varInfo);
        } else {
          $arrRet[$strIndice] = $varInfo;
        }
      }
      return $arrRet;
    } else {
      return $arrObjeto;
    }
  }
  
  /**
   * Funcion que corta una cadena, por palabras, en una más pequeña que se puede
   * imprimir en espacios más pequeños.
   * 
   * Si existe una palabra mayor a $intLimiteTamanio, esta funcion NO funcionará apropiadamente
   * 
   * @param string $strCadena             Cadena grande a partir
   * @param int    $intLimiteTamanio      Limite del tamaño a partir
   * @param int    $intRegresarEsteIndice Que parte de la cadena se regresará
   */
  static function SepararCadenaLarga($strCadena, $intLimiteTamanio, $intRegresarEsteIndice = 0) {
    // Primero quitamos espacios repetidos
    $strCadena = Variables::QuitarEspaciosRepetidos($strCadena);
    
    // Ahora hacemos una cadena con las cadenas anteriores pra empezar desde allí
    $strCadenaAnt = "";
    for($i = 0; $i < $intRegresarEsteIndice; $i++) {
      $strCadenaAnt .= Variables::SepararCadenaLarga($strCadena, $intLimiteTamanio, $i);
    }
    
    // Ahora quitamos a la cadena la cadena anterior
    $strCadenaAct = substr($strCadena, strlen($strCadenaAnt));
    
    // Obtenemos cada una de las palabras
    $arrPartesCadena = explode(" ", $strCadenaAct);
    
    // Ahora vamos armando la cadena
    $strRes = "";
    foreach($arrPartesCadena as $strCadenaTmp) {
      if(strlen($strRes . $strCadenaTmp . " ") >= $intLimiteTamanio) {
        // Adjuntar la siguiente parte haría a la cadena demasiado grande
        break;
      }
      $strRes .= $strCadenaTmp . " ";
    }
    
    return $strRes;
  }
  
  /**
   * Función que quita los elementos de una cadena de texto que se encuentren 
   * fuera del rango imprimible en ASCII (Caracteres desde 32 hasta 126)
   * 
   * @param string $strCadena Cadena a la que se le quitaran los caracteres
   * @return string Cadena sin los elementos
   */
  static function QuitarElementosASCIIFueraRango($strCadena, $bolSoloDejarAlfaNum = false) {
    $strRes = "";
    if($bolSoloDejarAlfaNum) {
      $strRes = preg_replace("/[^a-zA-Z0-9 \\.\\,\\-\\(\\)]+/", "", $strCadena);
    } else {
      for($i = 0; $i < strlen($strCadena); $i++) {
        if((ord($strCadena{$i}) >= 32) && (ord($strCadena{$i}) <= 126)) {
          $strRes .= $strCadena{$i};
        }
      }
    }
    return $strRes;
  }
}

function prent_r($arrCosas, $bolDie = true) {
  echo '<pre>';
  print_r($arrCosas);
  echo '</pre>';
  $bolDie && die;
}

?>